package com.zl.gmadmin.utils.jwt;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.zl.gmadmin.common.Consts;
import com.zl.gmadmin.common.Status;
import com.zl.gmadmin.configuration.secutity.jwt.JwtConfig;
import com.zl.gmadmin.exception.SecurityException;
import com.zl.gmadmin.utils.LogUtil;
import com.zl.gmadmin.vo.user.UserPrincipal;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author zhangliang
 * @version 1.0
 * @date 2021/2/3 15:05
 */
@Component
public class JwtUtil {


    private final JwtConfig jwtConfig;


    private final StringRedisTemplate stringRedisTemplate;

    public JwtUtil(JwtConfig jwtConfig, StringRedisTemplate stringRedisTemplate) {
        this.jwtConfig = jwtConfig;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 用户验证通过创建jwt
     * @param authentication
     * @param rememberMe
     * @return
     */
    public String createJWT(Authentication authentication, Boolean rememberMe) {

        UserPrincipal userPrincipal= (UserPrincipal) authentication.getPrincipal();
        return createJWT(rememberMe,userPrincipal.getId(),userPrincipal.getUsername(),userPrincipal.getComponent(),userPrincipal.getAuthorities());
    }

    /**
     * 创建jwt
     * @param rememberMe
     * @param id
     * @param username
     * @param component   菜单地址
     * @param authorities  角色
     * @return
     */
    private String createJWT(Boolean rememberMe, Long id, String username, List<String> component, Collection<? extends GrantedAuthority> authorities) {
        Date now=new Date();
        JwtBuilder builder= Jwts.builder().setId(id.toString())
                .setSubject(username)
                .setIssuedAt(now)  //登录日期
                .signWith(SignatureAlgorithm.HS256,jwtConfig.getKey()) //签证key
                .claim("component",component)   //存放数据
                .claim("authorities",authorities);

        //设置过期时间
        Long ttl=rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl();


        if (ttl >0) {
            builder.setExpiration(DateUtil.offsetMillisecond(now,ttl.intValue()));
        }

        //生成jwt
        String jwt=builder.compact();
        String redisKey= Consts.REDIS_JWT_KEY_PREFIX+username;
        // 将生成的JWT保存至Redis,以及redis过期时间,TimeUnit.MILLISECONDS毫米时间，在这里填错了,选成微秒，导致过期很快，找了很久问题
//        TimeUnit.DAYS          //天
//        TimeUnit.HOURS         //小时
//        TimeUnit.MINUTES       //分钟
//        TimeUnit.SECONDS       //秒
//        TimeUnit.MILLISECONDS  //毫秒
//        TimeUnit.NANOSECONDS   //毫微秒
//        TimeUnit.MICROSECONDS  //微秒
        stringRedisTemplate.opsForValue().set(redisKey,jwt,ttl, TimeUnit.MILLISECONDS);
        LogUtil.debug("redisKey="+redisKey);
        return jwt;
    }


    /**
     * 解析jwt和验证jwt是否和redis是否存在或一致
     * @param jwt
     * @return
     */
    public Claims parseJWT(String jwt){
        try {
            Claims claims=Jwts.parser().setSigningKey(jwtConfig.getKey()).parseClaimsJws(jwt).getBody();

            String username=claims.getSubject();
            String redisKey=Consts.REDIS_JWT_KEY_PREFIX+username;

            //校验redis中的JWT是否存在
            Long expire=stringRedisTemplate.getExpire(redisKey,TimeUnit.MILLISECONDS);
            if (Objects.isNull(expire)|| expire<0){
                LogUtil.debug("redis中不存在jwt");
                throw  new SecurityException(Status.UNAUTHORIZED);
            }

            // 校验redis中的JWT是否与当前的一致，不一致则代表用户已注销/用户在不同设备登录，均代表JWT已过期
            String redusToken=stringRedisTemplate.opsForValue().get(redisKey);
            if (!StrUtil.equals(jwt,redusToken)){
                throw new SecurityException(Status.UNAUTHORIZED);
            }

            return claims;
        }catch (ExpiredJwtException e) {
            LogUtil.error("Token 已过期");
            throw new SecurityException(Status.UNAUTHORIZED);
        } catch (UnsupportedJwtException e) {
            LogUtil.error("不支持的 Token");
            throw new SecurityException(Status.TOKEN_PARSE_ERROR);
        } catch (MalformedJwtException e) {
            LogUtil.error("Token 无效");
            throw new SecurityException(Status.TOKEN_PARSE_ERROR);
        } catch (SignatureException e) {
            LogUtil.error("无效的 Token 签名");
            throw new SecurityException(Status.TOKEN_PARSE_ERROR);
        } catch (IllegalArgumentException e) {
            LogUtil.error("Token 参数不存在");
            throw new SecurityException(Status.TOKEN_PARSE_ERROR);
        }
    }

    /**
     * 设置过期请求并清除redis
     * @param request
     */
    public void invalidateJWT(HttpServletRequest request){
        String jwt=getJwtFromRequest(request);
        LogUtil.debug("jwt={}",jwt);
        String username=getUsernameFromJWT(jwt);

        //从redis中清除jwt
        stringRedisTemplate.delete(Consts.REDIS_JWT_KEY_PREFIX+username);
    }

    /**
     * 从 request 的 header 中获取 JWT
     *
     * @param request 请求
     * @return JWT
     */
    public String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        LogUtil.debug("token:"+bearerToken);
        String b="Bearer ";
        if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith(b)) {
            return bearerToken.substring(7);
        }
        return null;
    }

    /**
     * 根据 jwt 获取用户名
     *
     * @param jwt JWT
     * @return 用户名
     */
    public String getUsernameFromJWT(String jwt) {
        Claims claims = parseJWT(jwt);
        return claims.getSubject();
    }

}
